#-*- coding: utf-8 -*-
"""
Classe permettant de travailler avec des nombres rationnels.
Par Guillaume HUYSMANS, 2013.

FIXME il y a encore des bugs...
"""

class Rational(object):
	#énorme
	def __init__(self, num=0, den=1):
		"""
		Creates a new Rational object from an existing instance,
		integer numbers or a single* string. They are automatically simplified.
		(*) if a string is given, den is simply ignored.
		"""
		if isinstance(num, Rational):
			#copie d'une instance existante
			#pas de simplification (c'est supposé être déjà fait)
			self.n = num.n
			self.d = num.d
		else:
			#nouvelle instance
			try:
				#basée sur une string ?
				if isinstance(num, str):
					#une string : on la parse
					if '/' in num:
						s = num.split('/')
						num = float(s[0])
						den = float(s[1])
					else:
						num = float(num)
						den = 1
				else:
					#on force les morceaux à être au moins des floats
					#une erreur sera provoquée si la conversion échoue...
					num = float(num)
					den = float(den)
			except:
				#si une erreur s'est produite, c'est forcément à cause d'un mauvais paramètre
				raise ValueError("Cannot create a rational with a numerator/denominator of "+str(type(num)))
			"""
			if not isinstance(num, int) and not isinstance(num, float) or isinstance(num, bool):
				raise ValueError("Cannot create a rational with a numerator of "+str(type(num)))
			elif not isinstance(den, int) or isinstance(den, bool):
				raise ValueError("Cannot create a rational with a denominator of "+str(type(den)))
			"""
			#fraction existante ?
			if den==0:
				raise ZeroDivisionError("Cannot create a rational with a null denominator")
			else:
				#FIXME ne fonctionne pas avec les négatifs !!
				#au pire y'aura pas d'itération.
				num = float(num)/den
				den = 1 #au départ, on a flottant/1 = flottant
				while num-int(num) > .0:
					den *= 10
					num *= 10
				#on veut avoir un vrai rationnel : num et den doivent être entiers !
				num = int(num)
				den = int(den)
				#simplification automatique avec le PGCD (diviser deux fois DONC barrer deux fois)
				p = Rational.pgcd(num, den)
				self.n = num/p
				self.d = den/p
	def __str__(self):
		"""Returns a text representation of the rational"""
		if (self.d == 1):
			return str(self.n)
		elif (self.n == 0):
			return '0'
		else:
			return str(self.n)+'/'+str(self.d)
	def __repr__(self):
		"""Returns a Python representation of the rational number"""
		return "Rational(" + str(self.n) + ", " + str(self.d) + ")"
	def __float__(self):
		"""Returns a float approximation of the rational"""
		return float(self.n)/self.d
	def __add__(self, other):
		other = Rational(other) #au pire, c'est une copie
		return Rational(self.n*other.d + other.n*self.d, self.d*other.d)
	def __radd__(self, other):
		return self + other
	def __neg__(self):
		return Rational(-self.n, self.d)
	def __sub__(self, other):
		return self + (-other)
	def __rsub__(self, other):
		return -self + other
	def __mul__(self, other):
		other = Rational(other)
		return Rational(self.n*other.n, self.d*other.d)
	def __rmul__(self, other):
		return self * other
	def inv(self):
		"""Returns the inverse of the rational"""
		return Rational(self.d, self.n)
	def __div__(self, other):
		return self * other.inv()
	def get_numerator(self):
		"""Returns its numerator"""
		return self.n
	def get_denominator(self):
		"""Returns its denominator"""
		return self.d
	@staticmethod
	def pgcd(x, y):
		"""Euclid's algorithm"""
		while y != 0:
			(x, y) = (y, x % y)
		return x

if __name__ == '__main__':
	n = Rational(1,2)
	n2 = Rational(n)
	n.n = 17
	print n2.n
